# Description: Functions for decompressing archives or installers

function Invoke-Extraction {
  param (
    [string]
    $Path,
    [string[]]
    $Name,
    [psobject]
    $Manifest,
    [Alias('Arch', 'Architecture')]
    [string]
    $ProcessorArchitecture
  )

  $uri = @(url $Manifest $ProcessorArchitecture)
  # 'extract_dir' and 'extract_to' are paired
  $extractDir = @(extract_dir $Manifest $ProcessorArchitecture)
  $extractTo = @(extract_to $Manifest $ProcessorArchitecture)
  $extracted = 0

  for ($i = 0; $i -lt $Name.Length; $i++) {
    # work out extraction method, if applicable
    $extractFn = $null
    switch -regex ($Name[$i]) {
      '\.zip$' {
        if ((Test-HelperInstalled -Helper 7zip) -or ((get_config 7ZIPEXTRACT_USE_EXTERNAL) -and (Test-CommandAvailable 7z))) {
          $extractFn = 'Expand-7zipArchive'
        } else {
          $extractFn = 'Expand-ZipArchive'
        }
        continue
      }
      '\.msi$' {
        $extractFn = 'Expand-MsiArchive'
        continue
      }
      '\.exe$' {
        if ($Manifest.innosetup) {
          $extractFn = 'Expand-InnoArchive'
        }
        continue
      }
      { Test-7zipRequirement -Uri $_ } {
        $extractFn = 'Expand-7zipArchive'
        continue
      }
    }
    if ($extractFn) {
      $fnArgs = @{
        Path            = Join-Path $Path $Name[$i]
        DestinationPath = Join-Path $Path $extractTo[$extracted]
        ExtractDir      = $extractDir[$extracted]
      }
      Write-Host 'Extracting ' -NoNewline
      Write-Host $(url_remote_filename $uri[$i]) -ForegroundColor Cyan -NoNewline
      Write-Host ' ... ' -NoNewline
      & $extractFn @fnArgs -Removal
      Write-Host 'done.' -ForegroundColor Green
      $extracted++
    }
  }
}

function Expand-7zipArchive {
  [CmdletBinding()]
  param (
    [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
    [String]
    $Path,
    [Parameter(Position = 1)]
    [String]
    $DestinationPath = (Split-Path $Path),
    [String]
    $ExtractDir,
    [Parameter(ValueFromRemainingArguments = $true)]
    [String]
    $Switches,
    [ValidateSet('All', 'Skip', 'Rename')]
    [String]
    $Overwrite,
    [Switch]
    $Removal
  )
  if ((get_config USE_EXTERNAL_7ZIP)) {
    try {
      $7zPath = (Get-Command '7z' -CommandType Application -ErrorAction Stop | Select-Object -First 1).Source
    } catch [System.Management.Automation.CommandNotFoundException] {
      abort "`nCannot find external 7-Zip (7z.exe) while 'use_external_7zip' is 'true'!`nRun 'scoop config use_external_7zip false' or install 7-Zip manually and try again."
    }
  } else {
    $7zPath = Get-HelperPath -Helper 7zip
  }
  $LogPath = "$(Split-Path $Path)\7zip.log"
  $DestinationPath = $DestinationPath.TrimEnd('\')
  $ArgList = @('x', $Path, "-o$DestinationPath", '-xr!*.nsis', '-y')
  $IsTar = ((strip_ext $Path) -match '\.tar$') -or ($Path -match '\.t[abgpx]z2?$')
  if (!$IsTar -and $ExtractDir) {
    $ArgList += "-ir!$ExtractDir\*"
  }
  if ($Switches) {
    $ArgList += (-split $Switches)
  }
  switch ($Overwrite) {
    'All' { $ArgList += '-aoa' }
    'Skip' { $ArgList += '-aos' }
    'Rename' { $ArgList += '-aou' }
  }
  $Status = Invoke-ExternalCommand $7zPath $ArgList -LogPath $LogPath
  if (!$Status) {
    abort "Failed to extract files from $Path.`nLog file:`n  $(friendly_path $LogPath)`n$(new_issue_msg $app $bucket 'decompress error')"
  }
  if ($IsTar) {
    # Check for tar
    $Status = Invoke-ExternalCommand $7zPath @('l', $Path) -LogPath $LogPath
    if ($Status) {
      # get inner tar file name
      $TarFile = (Select-String -Path $LogPath -Pattern '[^ ]*tar$').Matches.Value
      Expand-7zipArchive -Path "$DestinationPath\$TarFile" -DestinationPath $DestinationPath -ExtractDir $ExtractDir -Removal
    } else {
      abort "Failed to list files in $Path.`nNot a 7-Zip supported archive file."
    }
  }
  if (!$IsTar -and $ExtractDir) {
    movedir "$DestinationPath\$ExtractDir" $DestinationPath | Out-Null
    # Remove temporary directory
    Remove-Item "$DestinationPath\$($ExtractDir -replace '[\\/].*')" -Recurse -Force -ErrorAction Ignore
  }
  if (Test-Path $LogPath) {
    Remove-Item $LogPath -Force
  }
  if ($Removal) {
    if (($Path -replace '.*\.([^\.]*)$', '$1') -eq '001') {
      # Remove splited 7-zip archive parts
      Get-ChildItem "$($Path -replace '\.[^\.]*$', '').???" | Remove-Item -Force
    } elseif (($Path -replace '.*\.part(\d+)\.rar$', '$1')[-1] -eq '1') {
      # Remove splitted RAR archive parts
      Get-ChildItem "$($Path -replace '\.part(\d+)\.rar$', '').part*.rar" | Remove-Item -Force
    } else {
      # Remove original archive file
      Remove-Item $Path -Force
    }
  }
}

function Expand-ZstdArchive {
  [CmdletBinding()]
  param (
    [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
    [String]
    $Path,
    [Parameter(Position = 1)]
    [String]
    $DestinationPath = (Split-Path $Path),
    [String]
    $ExtractDir,
    [Parameter(ValueFromRemainingArguments = $true)]
    [String]
    $Switches,
    [Switch]
    $Removal
  )
  # TODO: Remove this function after 2024/12/31
  Show-DeprecatedWarning $MyInvocation 'Expand-7zipArchive'
  Expand-7zipArchive -Path $Path -DestinationPath $DestinationPath -ExtractDir $ExtractDir -Switches $Switches -Removal:$Removal
}

function Expand-MsiArchive {
  [CmdletBinding()]
  param (
    [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
    [String]
    $Path,
    [Parameter(Position = 1)]
    [String]
    $DestinationPath = (Split-Path $Path),
    [String]
    $ExtractDir,
    [Parameter(ValueFromRemainingArguments = $true)]
    [String]
    $Switches,
    [Switch]
    $Removal
  )
  $DestinationPath = $DestinationPath.TrimEnd('\')
  if ($ExtractDir) {
    $OriDestinationPath = $DestinationPath
    $DestinationPath = "$DestinationPath\_tmp"
  }
  if ((get_config USE_LESSMSI)) {
    $MsiPath = Get-HelperPath -Helper Lessmsi
    $ArgList = @('x', $Path, "$DestinationPath\")
  } else {
    $MsiPath = 'msiexec.exe'
    $ArgList = @('/a', $Path, '/qn', "TARGETDIR=$DestinationPath\SourceDir")
  }
  $LogPath = "$(Split-Path $Path)\msi.log"
  if ($Switches) {
    $ArgList += (-split $Switches)
  }
  $Status = Invoke-ExternalCommand $MsiPath $ArgList -LogPath $LogPath
  if (!$Status) {
    abort "Failed to extract files from $Path.`nLog file:`n  $(friendly_path $LogPath)`n$(new_issue_msg $app $bucket 'decompress error')"
  }
  if ($ExtractDir -and (Test-Path "$DestinationPath\SourceDir")) {
    movedir "$DestinationPath\SourceDir\$ExtractDir" $OriDestinationPath | Out-Null
    Remove-Item $DestinationPath -Recurse -Force
  } elseif ($ExtractDir) {
    movedir "$DestinationPath\$ExtractDir" $OriDestinationPath | Out-Null
    Remove-Item $DestinationPath -Recurse -Force
  } elseif (Test-Path "$DestinationPath\SourceDir") {
    movedir "$DestinationPath\SourceDir" $DestinationPath | Out-Null
  }
  if (($DestinationPath -ne (Split-Path $Path)) -and (Test-Path "$DestinationPath\$(fname $Path)")) {
    Remove-Item "$DestinationPath\$(fname $Path)" -Force
  }
  if (Test-Path $LogPath) {
    Remove-Item $LogPath -Force
  }
  if ($Removal) {
    # Remove original archive file
    Remove-Item $Path -Force
  }
}

function Expand-InnoArchive {
  [CmdletBinding()]
  param (
    [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
    [String]
    $Path,
    [Parameter(Position = 1)]
    [String]
    $DestinationPath = (Split-Path $Path),
    [String]
    $ExtractDir,
    [Parameter(ValueFromRemainingArguments = $true)]
    [String]
    $Switches,
    [Switch]
    $Removal
  )
  $LogPath = "$(Split-Path $Path)\innounp.log"
  $ArgList = @('-x', "-d$DestinationPath", $Path, '-y')
  switch -Regex ($ExtractDir) {
    '^[^{].*' { $ArgList += "-c{app}\$ExtractDir" }
    '^{.*' { $ArgList += "-c$ExtractDir" }
    Default { $ArgList += '-c{app}' }
  }
  if ($Switches) {
    $ArgList += (-split $Switches)
  }
  $Status = Invoke-ExternalCommand (Get-HelperPath -Helper Innounp) $ArgList -LogPath $LogPath
  if (!$Status) {
    abort "Failed to extract files from $Path.`nLog file:`n  $(friendly_path $LogPath)`n$(new_issue_msg $app $bucket 'decompress error')"
  }
  if (Test-Path $LogPath) {
    Remove-Item $LogPath -Force
  }
  if ($Removal) {
    # Remove original archive file
    Remove-Item $Path -Force
  }
}

function Expand-ZipArchive {
  [CmdletBinding()]
  param (
    [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
    [String]
    $Path,
    [Parameter(Position = 1)]
    [String]
    $DestinationPath = (Split-Path $Path),
    [String]
    $ExtractDir,
    [Switch]
    $Removal
  )
  if ($ExtractDir) {
    $OriDestinationPath = $DestinationPath
    $DestinationPath = "$DestinationPath\_tmp"
  }
  # Disable progress bar to gain performance
  $oldProgressPreference = $ProgressPreference
  $global:ProgressPreference = 'SilentlyContinue'

  # Compatible with Pscx v3 (https://github.com/Pscx/Pscx) ('Microsoft.PowerShell.Archive' is not needed for Pscx v4)
  Microsoft.PowerShell.Archive\Expand-Archive -Path $Path -DestinationPath $DestinationPath -Force

  $global:ProgressPreference = $oldProgressPreference
  if ($ExtractDir) {
    movedir "$DestinationPath\$ExtractDir" $OriDestinationPath | Out-Null
    Remove-Item $DestinationPath -Recurse -Force
  }
  if ($Removal) {
    # Remove original archive file
    Remove-Item $Path -Force
  }
}

function Expand-DarkArchive {
  [CmdletBinding()]
  param (
    [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
    [String]
    $Path,
    [Parameter(Position = 1)]
    [String]
    $DestinationPath = (Split-Path $Path),
    [Parameter(ValueFromRemainingArguments = $true)]
    [String]
    $Switches,
    [Switch]
    $Removal
  )
  $LogPath = "$(Split-Path $Path)\dark.log"
  $DarkPath = Get-HelperPath -Helper Dark
  if ((Split-Path $DarkPath -Leaf) -eq 'wix.exe') {
    $ArgList = @('burn', 'extract', $Path, '-out', $DestinationPath, '-outba', "$DestinationPath\UX")
  } else {
    $ArgList = @('-nologo', '-x', $DestinationPath, $Path)
  }
  if ($Switches) {
    $ArgList += (-split $Switches)
  }
  $Status = Invoke-ExternalCommand $DarkPath $ArgList -LogPath $LogPath
  if (!$Status) {
    abort "Failed to extract files from $Path.`nLog file:`n  $(friendly_path $LogPath)`n$(new_issue_msg $app $bucket 'decompress error')"
  }
  if (Test-Path "$DestinationPath\WixAttachedContainer") {
    Rename-Item "$DestinationPath\WixAttachedContainer" 'AttachedContainer' -ErrorAction Ignore
  } else {
    if (Test-Path "$DestinationPath\AttachedContainer\a0") {
      $Xml = [xml](Get-Content -Raw "$DestinationPath\UX\manifest.xml" -Encoding utf8)
      $Xml.BurnManifest.UX.Payload | ForEach-Object {
        Rename-Item "$DestinationPath\UX\$($_.SourcePath)" $_.FilePath -ErrorAction Ignore
      }
      $Xml.BurnManifest.Payload | ForEach-Object {
        Rename-Item "$DestinationPath\AttachedContainer\$($_.SourcePath)" $_.FilePath -ErrorAction Ignore
      }
    }
  }
  if (Test-Path $LogPath) {
    Remove-Item $LogPath -Force
  }
  if ($Removal) {
    # Remove original archive file
    Remove-Item $Path -Force
  }
}
